package com.lankystar.common.interceptor;

import java.sql.Connection;
import java.sql.PreparedStatement;
import java.sql.ResultSet;
import java.sql.SQLException;
import java.util.List;
import java.util.Properties;

import org.apache.ibatis.binding.MapperMethod;
import org.apache.ibatis.binding.MapperMethod.ParamMap;
import org.apache.ibatis.executor.Executor;
import org.apache.ibatis.executor.parameter.ParameterHandler;
import org.apache.ibatis.mapping.BoundSql;
import org.apache.ibatis.mapping.MappedStatement;
import org.apache.ibatis.mapping.ParameterMapping;
import org.apache.ibatis.plugin.Interceptor;
import org.apache.ibatis.plugin.Intercepts;
import org.apache.ibatis.plugin.Invocation;
import org.apache.ibatis.plugin.Plugin;
import org.apache.ibatis.plugin.Signature;
import org.apache.ibatis.scripting.defaults.DefaultParameterHandler;
import org.apache.ibatis.session.ResultHandler;
import org.apache.ibatis.session.RowBounds;

import com.lankystar.common.Page;
import com.lankystar.common.util.ReflectUtil;

@Intercepts({ @Signature(type = Executor.class, method = "query", args = { MappedStatement.class, Object.class,
		RowBounds.class, ResultHandler.class }) })
public class Pagination4MysqlInterceptor implements Interceptor {

	@Override
	public Object intercept(Invocation invocation) throws Throwable {
		MappedStatement mappedStatement = (MappedStatement) invocation.getArgs()[0];
		Object params = invocation.getArgs()[1];
		BoundSql boundSql = mappedStatement.getBoundSql(params);
		Page<?> page = null;
		if (params instanceof MapperMethod.ParamMap<?>) {
			MapperMethod.ParamMap<?> map = (ParamMap<?>) params;
			if (!map.containsKey("page")) {
				return invocation.proceed();
			}
			Object _page = map.get("page");
			if (_page instanceof Page<?>) {
				page = (Page<?>) _page;
			}
		} else if (params instanceof Page<?>) {
			page = (Page<?>) params;
		}
		if (page != null && page.getPages() == -1 && page.getTotal() == -1) {
			Connection connection = mappedStatement.getConfiguration().getEnvironment().getDataSource().getConnection();
			String sql = boundSql.getSql();
			setTotalRecord(page, mappedStatement, connection, params);
			String pageSql = getPageSql(page, sql);
			ReflectUtil.setFieldValue(boundSql, "sql", pageSql);
		}
		return invocation.proceed();
	}

	@Override
	public Object plugin(Object arg0) {
		return Plugin.wrap(arg0, this);
	}

	@Override
	public void setProperties(Properties arg0) {
	}

	private String getPageSql(Page<?> page, String sql) {
		int offset = (page.getPage() - 1) * page.getPages();
		StringBuilder sqlBuild = new StringBuilder(sql);
		sqlBuild.append(" limit ").append(offset).append(",").append(page.getPageSize());
		return sqlBuild.toString();
	}

	private void setTotalRecord(Page<?> page, MappedStatement mappedStatement, Connection connection, Object params) {
		BoundSql boundSql = mappedStatement.getBoundSql(params);
		String sql = boundSql.getSql();
		String countSql = this.getCountSql(sql);
		List<ParameterMapping> parameterMappings = boundSql.getParameterMappings();
		BoundSql countBoundSql = new BoundSql(mappedStatement.getConfiguration(), countSql, parameterMappings, params);
		ParameterHandler parameterHandler = new DefaultParameterHandler(mappedStatement, params, countBoundSql);
		PreparedStatement pstmt = null;
		ResultSet rs = null;
		try {
			pstmt = connection.prepareStatement(countSql);
			parameterHandler.setParameters(pstmt);
			rs = pstmt.executeQuery();
			if (rs.next()) {
				int totalRecord = rs.getInt(1);
				int pageSize = page.getPageSize();
				int pages = totalRecord / pageSize;
				if (totalRecord % pageSize > 0) {
					pages++;
				}
				page.setTotal(totalRecord);
				page.setPages(pages);
			}
		} catch (SQLException e) {
			e.printStackTrace();
		} finally {
			try {
				if (rs != null)
					rs.close();
				if (pstmt != null)
					pstmt.close();
			} catch (SQLException e) {
				e.printStackTrace();
			}
		}
	}

	/**
	 * 根据原Sql语句获取对应的查询总记录数的Sql语句
	 * 
	 * @param sql
	 * @return
	 */
	private String getCountSql(String sql) {
		int index = sql.indexOf("from");
		return "select count(1) " + sql.substring(index);
	}
}
